﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Printing;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using Aspose.Cells;
using Aspose.Cells.Drawing;
using CardGenerator.UI.Pages;
using Microsoft.Win32;
using OfficeOpenXml;
using OfficeOpenXml.Drawing;
using OfficeOpenXml.Style;
using PropertyChanged;
using SkiaSharp;
using WPFFolderBrowser;

namespace CardGenerator.ViewModel;

[AddINotifyPropertyChangedInterface]
public class OperationPageViewModel : BaseViewModel
{
    public ObservableCollection<Dictionary<String, String>> Data { get; private set; } = new();
    public List<Dictionary<String, Picture>> Pictures = new();

    public CardTemplate? SelectedTemplate { get; set; }
    public ExcelPackage? ExcelPackage { get; private set; }
    public ExcelWorksheet? SelectedWorksheet { get; set; }
    public UInt32 Progress { get; set; } = 0;
    public ICommand OpenExcelFileCommand { get; private set; }
    public ICommand ExportAsPdfCommand { get; private set; }
    public String SelectedExportFormat { get; set; }
    public String[] InstalledPrinters { get; set; }
    public ICommand RefreshPrinters { get; private set; }
    public String SelectedPrinter { get; set; }
    public ICommand Print { get; private set; }

    public OperationPageViewModel()
    {
        ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial;
        this.OpenExcelFileCommand = new ClickCommand(_ =>
        {
            var dialog = new OpenFileDialog
            {
                Filter = "Excel file (versions of Excel 2007 or higher)|*.xlsx",
                Multiselect = false,
                Title = "Please select excel file that you want to process"
            };
            if (!(dialog.ShowDialog() ?? false)) return;
            OpenFile(dialog.FileName);
            Worksheet s;
        });
        this.ExportAsPdfCommand = new ClickCommand(async _ =>
        {
            if (!(SelectedTemplate?.IsTemplateFileExists() ?? false))
            {
                MessageBox.Show("你已选择的模板没有模板文件， 请先添加模板文件");
                return;
            }

            var dialog = new WPFFolderBrowserDialog
            {
                Title = "Please select some location that you want to store the generated PDF files"
            };
            if (!(dialog.ShowDialog() ?? false)) return;
            var outputLocation = dialog.FileName!;
            await GenerateImage(this.Data.ToArray(), SelectedTemplate, this.SelectedExportFormat, async (bytes, fileName) =>
            {
                await File.WriteAllBytesAsync(Path.Join(outputLocation, fileName), bytes);
            });
            MessageBox.Show("成功");
        });
        this.RefreshPrinters = new ClickCommand(_ =>
        {
            this.InstalledPrinters = new string[PrinterSettings.InstalledPrinters.Count];
            for (var i = 0; i < this.InstalledPrinters.Length; i++)
            {
                this.InstalledPrinters[i] = PrinterSettings.InstalledPrinters[i];
            }
        });
        this.RefreshPrinters.Execute(null);
        this.Print = new ClickCommand(_ =>
        {
            MessageBox.Show("请联系开发者");
        });
    }

    public async Task GenerateImage(Dictionary<String, String>[] data, CardTemplate cardTemplate, String exportFormat, Func<Byte[], String, Task> onDataReady)
    {
        List<Task> tasks = new();
        var analyzer = new TemplateAnalyzer();
        analyzer.ReadAndAnalyze(cardTemplate);

        var areaUsageIndex = 0;
        var regexes = this.Data.First().Keys.Select(it => new { key = it, expression = new Regex($"{{{{{it}}}}}") });
        this.Progress = 0;

        var templateBook = new Workbook(cardTemplate.GetTemplateFilePath());
        var templateSheet = templateBook.Worksheets.First();
        await Task.Run(async () =>
        {
            var saveAction = async (String fileName) =>
            {
                if (fileName.Length > 128) fileName = fileName.Substring(0, 128);
                await using (MemoryStream ms = new MemoryStream())
                {
                    await analyzer.Workbook.SaveAsAsync(ms);
                    Workbook workbook = new Workbook(ms);
                    await using (MemoryStream buffer = new MemoryStream())
                    {
                        var format = exportFormat.ToLower().Trim();
                        if (format == "pdf" || format == "html")
                        {
                            workbook.Save(buffer, format == "pdf" ? SaveFormat.Pdf : SaveFormat.Html);
                            // await File.WriteAllBytesAsync(Path.Join(outputLocation, $"{fileName}.{this.SelectedExportFormat}"), pdfStream.ToArray());
                            await onDataReady(buffer.ToArray(), Path.Join($"{fileName}.{this.SelectedExportFormat}"));
                        }
                        else
                        {
                            workbook.Save(buffer, SaveFormat.Jpg);
                            var formats = new Dictionary<String, SKEncodedImageFormat>
                            {
                                    {"jpg", SKEncodedImageFormat.Jpeg},
                                    {"png", SKEncodedImageFormat.Png},
                                    {"webp", SKEncodedImageFormat.Webp},
                                    {"bmp", SKEncodedImageFormat.Bmp},
                            };
                            var imageData = buffer.ToArray();
                            Func<Task> action = async () =>
                            {
                                // try
                                // {
                                    var bytes = await removeEmptyBorders(imageData, formats[format]);
                                    // if (bytes != null) await File.WriteAllBytesAsync(Path.Join(outputLocation, $"{fileName}.{format.ToLower()}"), bytes);
                                    if (bytes != null) await onDataReady(bytes, Path.Join($"{fileName}.{format.ToLower()}"));
                                // }
                                // catch (Exception ex)
                                // {
                                //     throw new Exception($"File name: {fileName}, exception is: {ex.Message}, stackTrace: {ex.StackTrace}");
                                // }
                            };
                            tasks.Add(Task.Run(action));
                            // await action();
                            // tasks.Add(Task.CompletedTask);
                        }
                    }
                }
            };
            var dataIndex = -1;
            foreach (var dictionary in data)
            {
                dataIndex++;

                foreach (var cell in analyzer.Areas[areaUsageIndex].GetCells())
                {
                    var value = cell.GetCellValue<String?>() ?? "";
                    foreach (var regex in regexes)
                    {
                        if (regex.expression.Match(value).Success)
                        {
                            value = regex.expression.Replace(value, dictionary[regex.key] ?? "");
                            cell.SetCellValue(0, 0, value);
                            if (Pictures[dataIndex].ContainsKey(regex.key))
                            {
                                var picture = Pictures[dataIndex][regex.key];
                                using (var memoryStream = new MemoryStream())
                                {
                                    var imageFormats = new Dictionary<ImageType, ePictureType>
                                    {
                                            {ImageType.Jpeg, ePictureType.Jpg},
                                            {ImageType.Bmp, ePictureType.Bmp},
                                            {ImageType.Emf, ePictureType.Emf},
                                            {ImageType.Gif, ePictureType.Gif},
                                            // {ImageType.Unknown, ePictureType.Ico},
                                            {ImageType.Png, ePictureType.Png},
                                            {ImageType.Tiff, ePictureType.Tif},
                                            {ImageType.Wmf, ePictureType.Wmf},
                                    };
                                    memoryStream.Write(picture.Data);
                                    var excelPicture = await cell.Worksheet.Drawings.AddPictureAsync(Guid.NewGuid().ToString(), memoryStream, imageFormats[picture.ImageType]);

                                    var imageCells = templateSheet.Cells[cell.Address].GetMergedRange();
                                    var cellHeight = 0;
                                    var cellWidth = 0;
                                    for (var rowIndex = cell.Start.Row - 1; rowIndex < imageCells.RowCount + (cell.Start.Row - 1); rowIndex++)
                                    {
                                        cellHeight += templateSheet.Cells.GetRowHeightPixel(rowIndex);
                                    }
                                    for (var columnIndex = cell.Start.Column - 1; columnIndex < imageCells.ColumnCount + (cell.Start.Column - 1); columnIndex++)
                                    {
                                        cellWidth += templateSheet.Cells.GetColumnWidthPixel(columnIndex);
                                    }
                                    excelPicture.SetSize(cellWidth - 4, cellHeight - 4);
                                    excelPicture.SetPosition(cell.Start.Row - 1, 2, cell.Start.Column - 1, 2);
                                }
                            }
                        }
                    }
                }
                areaUsageIndex++;
                if (areaUsageIndex == analyzer.Areas.Count)
                {
                    //await saveAction($"{dataIndex}-{dictionary.Values.Aggregate((a, b) => $"{a}-{b}")}");
                    await saveAction($"{dataIndex}");
                    analyzer.ReadAndAnalyze(cardTemplate);
                    areaUsageIndex = 0;
                }
                Application.Current.Dispatcher.Invoke(() =>
                {
                    Progress++;
                });
            }
            //if (areaUsageIndex > 0) await saveAction($"{dataIndex}-{data.First().Values.Aggregate((a, b) => $"{a}-{b}")}");
            if (areaUsageIndex > 0) await saveAction($"{dataIndex}");
        });
        await Task.WhenAll(tasks.ToArray());
    }

    public async Task<Byte[]?> removeEmptyBorders(Byte[] jpegStream, SKEncodedImageFormat format)
    {
        Byte[] bytes = null;
        using (var skBitmap = SKBitmap.Decode(jpegStream))
        {
            using (var newBitmap = skBitmap.RemoveEmptyBackgroundBounds())
            {
                using (var data = newBitmap.Encode(format, 100))
                {
                    bytes = data.ToArray();
                }
            }
        }
        return bytes;
    }

    public async Task OpenFile(String path)
    {
        this.ExcelPackage?.Dispose();
        ExcelPackage = new ExcelPackage(path);
        await ExcelPackage.LoadAsync(path);
    }

    public Dictionary<String, String>[] getSelectedWorksheetAsKeyValuePair()
    {
        var list = new List<Dictionary<String, String>>();
        var titleRowIndex = 0;
        var columns = new String[this.SelectedWorksheet?.Dimension?.Columns ?? 0];

        for (var columnIndex = 0; columnIndex < SelectedWorksheet?.Dimension.Columns; columnIndex++)
        {
            var cell = SelectedWorksheet.Cells[1, columnIndex + 1];
            if (cell.Value == null) break;
            columns[columnIndex] = cell.Value.ToString().Trim();
        }
        var indexOfNull = columns.ToList().IndexOf(null);
        if (indexOfNull > 0 && columns.Skip(indexOfNull).Any(item => item != null))
        {
            MessageBox.Show("数据文件的第一个行是作为数据的标题， 但是这个标题行是必须是不要空");
            return list.ToArray();
        }
        if (indexOfNull > 0) columns = columns.Take(indexOfNull).ToArray();

        for (var rowIndex = titleRowIndex + 1; rowIndex < SelectedWorksheet?.Dimension?.Rows; rowIndex++)
        {
            var row = new Dictionary<String, String>();
            for (var a = 0; a < columns.Length; a++) row.Add(columns[a], null);
            for (var columnIndex = 0; columnIndex < columns.Length; columnIndex++)
            {
                var cell = SelectedWorksheet.Cells[rowIndex + 1, columnIndex + 1];
                row[columns[columnIndex]] = cell.Value?.ToString();
            }

            list.Add(row);
        }

        if (list.Count > 0)
        {
            this.Pictures.Clear();
            this.Pictures.AddRange(list.Select(it => new Dictionary<string, Picture>()));
            var book = new Workbook(ExcelPackage.File.FullName);
            var sheet = book.Worksheets[this.SelectedWorksheet.Name];

            int getRowIndexByY(Int32 y)
            {
                var totalHeight = 0;
                var rowIndex = 0;
                for (rowIndex = 0; rowIndex < sheet.Cells.Rows.Count; rowIndex++)
                {
                    totalHeight += sheet.Cells.GetRowHeightPixel(rowIndex);
                    if (totalHeight >= y) break;
                }
                return rowIndex - (titleRowIndex + 1);
            }
            int getColumnIndexByX(Int32 x)
            {
                var totalWidth = 0;
                var columnIndex = 0;
                for (columnIndex = 0; columnIndex < columns.Length; columnIndex++)
                {
                    totalWidth += sheet.Cells.GetColumnWidthPixel(columnIndex);
                    if (totalWidth >= x) break;
                }
                return columnIndex;
            }

            var pictures = sheet.Pictures.Select(picture => new
            {
                picture = picture,
                dataIndex = getRowIndexByY(picture.Y),
                columnIndex = getColumnIndexByX(picture.X),
                key = list.First().Keys.ToArray()[getColumnIndexByX(picture.X)],
            }).ToArray();
            foreach (var grouping in pictures.GroupBy(it => it.dataIndex))
            {
                foreach (var item in grouping)
                {
                    Pictures[grouping.Key][item.key] = item.picture;
                }
            }
        }

        return list.ToArray();
    }

    /// <summary>
    /// Initialize view model by it's window.
    /// </summary>
    /// <param name="page"></param>
    public void Initialize(OprtationPage page)
    {
        this.PropertyChanged += (sender, args) =>
        {
            if (args.PropertyName == nameof(SelectedWorksheet))
            {
                var data = this.getSelectedWorksheetAsKeyValuePair();
                this.Data.Clear();
                foreach (var item in data)
                {
                    this.Data.Add(item);
                }
            }
        };
    }
}

/// <summary>
/// As you can see the name, this is really Template analyzer
/// </summary>
public class TemplateAnalyzer
{
    public List<Area> Areas { get; private set; } = new();
    public ExcelPackage Workbook;
    public ExcelWorksheet Worksheet;

    public async void ReadAndAnalyze(CardTemplate template)
    {
        this.Workbook = new ExcelPackage(template.GetTemplateFilePath());
        this.Worksheet = this.Workbook.Workbook.Worksheets.First();
        var cells = this.Worksheet.Cells.Where(it => it.IsBorderCell()).ToArray();

        if (cells.Length == 0)
        {
            MessageBox.Show("Template is empty");
            return;
        }
        this.Areas.Clear();
        foreach (var cell in cells)
        {
            var area = Areas.FirstOrDefault(item => item.Contains(cell));
            if (area == null)
            {
                area = new Area(this.Worksheet, cells, cell, cell, cell, cell);
                area.ExpandSelf();
                this.Areas.Add(area);
            }
        }

        var info = Areas.Select(it => it.ToString()).ToArray();
    }
}

/// <summary>
/// Describes Template item area on the template xlsx (.tp file is actually xlsx file) file
/// </summary>
public class Area
{
    public Area(ExcelWorksheet worksheet, ExcelAddressBase[] cells, ExcelAddressBase topLeft, ExcelAddressBase topRight, ExcelAddressBase bottomLeft, ExcelAddressBase bottomRight)
    {
        TopLeft = topLeft;
        TopRight = topRight;
        BottomLeft = bottomLeft;
        BottomRight = bottomRight;
        Worksheet = worksheet;
        this.cells = cells;
    }

    public ExcelWorksheet Worksheet { get; private set; }
    public ExcelAddressBase[] cells { get; private set; }
    public ExcelAddressBase TopLeft { get; private set; }
    public ExcelAddressBase TopRight { get; private set; }
    public ExcelAddressBase BottomLeft { get; private set; }
    public ExcelAddressBase BottomRight { get; private set; }

    public Boolean Contains(ExcelAddressBase cell)
    {
        return cell.End.Column >= TopLeft.End.Column && cell.End.Column <= TopRight.End.Column && cell.End.Row >= TopLeft.End.Row && cell.End.Row <= BottomLeft.End.Row;
    }

    public void ExpandSelf()
    {
        while (TopRight.End.Column < Worksheet.Dimension.End.Column)
        {
            var cell = Worksheet.Cells[TopRight.End.Row, TopRight.End.Column + 1];
            if (cell.IsBorderCell()) this.TopRight = cell;
            else break;
        }

        while (BottomLeft.End.Row < Worksheet.Dimension.End.Row)
        {
            var cell = Worksheet.Cells[BottomLeft.End.Row + 1, BottomLeft.End.Column];
            if (cell.IsBorderCell()) this.BottomLeft = cell;
            else break;
        }
        this.BottomRight = Worksheet.Cells[BottomLeft.End.Row, TopRight.End.Column];
    }

    public ExcelRange[] GetCells()
    {
        return this.cells.Where(this.Contains).Select(it => this.Worksheet.Cells[it.End.Row, it.End.Column]).ToArray();
    }

    public override string ToString() => $"TopLeft: {TopLeft}, TopRight: {TopRight}, BottomLeft: {BottomLeft}, BottomRight: {BottomRight}";
}

internal static class CellExtension
{
    public static Boolean IsBorderCell(this ExcelRangeBase self)
    {
        var border = self.Style.Border;
        var isBorderCell = border.Top.Style != ExcelBorderStyle.None;
        isBorderCell |= border.Right.Style != ExcelBorderStyle.None;
        isBorderCell |= border.Bottom.Style != ExcelBorderStyle.None;
        isBorderCell |= border.Left.Style != ExcelBorderStyle.None;
        return isBorderCell;
    }
}
